﻿using System;
using System.Collections.Generic;
using NodeBasedSkillEditor.View;
using NodeBasedSkillEditor.ViewModel;
using XDFLib.Collections;

namespace NodeBasedSkillEditor.Model
{
    public class LinkManager
    {
        private VM_DAG _DAGVM;
        /// <summary>
        /// 这维护的是来自DAG View Model的LinkData;
        /// </summary>
        public Dictionary<uint, HashSet<uint>> WorkingLinkData { get; private set; }

        private BiSet<uint> _linkingMap = new BiSet<uint>();
        private Dictionary<(uint, uint), LinkControl> _linkMap = new Dictionary<(uint, uint), LinkControl>();

        public bool AllowCyclicFlow { get; private set; } = false;

        /// 记录节点之间节点的连通性，用于无环检查
        /// 第一层字典表示一个节点出发后直达的节点
        /// 第二层字典表示对于一个可直达的目标节点，有多少条Link
        private Dictionary<VM_Node, Dictionary<VM_Node, HashSet<LinkControl>>> _nodeTopology = new Dictionary<VM_Node, Dictionary<VM_Node, HashSet<LinkControl>>>();

        private ContextMap _contextMap = new ContextMap();

        /// <summary>
        /// 
        /// </summary>
        /// <param name="dag"></param>
        /// <param name="linkData"></param>
        /// <param name="allowCyclicFlow">是否允许Flow端口形成环状连接</param>
        public LinkManager(VM_DAG dag, Dictionary<uint, HashSet<uint>> linkData, bool allowCyclicFlow = false)
        {
            _DAGVM = dag;
            WorkingLinkData = linkData;
            AllowCyclicFlow = allowCyclicFlow;
            _DAGVM.OnAddNode += OnAddNodeToDAG;
            _DAGVM.OnRemoveNode += OnRemoveNodeFromDAG;
        }

        public Dictionary<uint, HashSet<uint>> GenerateLinkData()
        {
            Dictionary<uint, HashSet<uint>> result = new();
            foreach (var l in _linkMap)
            {
                var outPortId = l.Key.Item1;
                var inPortId = l.Key.Item2;

                HashSet<uint> destPortIds;
                if (!result.TryGetValue(outPortId, out destPortIds))
                {
                    destPortIds = new();
                    result.Add(outPortId, destPortIds);
                }
                destPortIds.Add(inPortId);
            }
            return result;
        }

        public bool CanPortsBeLinked(VM_Port port1, VM_Port port2)
        {
            var ports = MakeSurePortsOrder(port1, port2);
            if (!ports.Item3)
            {
                return false;
            }
            port1 = ports.Item1;
            port2 = ports.Item2;


            var ioTypeCheck = port1.IOType != EPortIOType.Unknown
                        && port2.IOType != EPortIOType.Unknown
                        && port1.IOType != port2.IOType;

            if (!ioTypeCheck)
            {
                return false;
            }

            // FlowPort不需要做更多的检查
            if (port1.IsFlowPort && port2.IsFlowPort)
            {
                /// 环状连接检查
                return PassAcyclicCheck(port1, port2);
            }

            /// 端口双方必须同时为List或都不是才能连接
            if (port1.IsListPort != port2.IsListPort)
            {
                return false;
            }

            /// 两个List端口连接时需要通过Context兼容性测试
            if (port1.IsListPort && port2.IsListPort)
            {
                var context1 = _contextMap.GetPortContextId(port1.Id);
                var context2 = _contextMap.GetPortContextId(port2.Id);
                if (!DAGHelper.AreContextsCompatible(context1, context2))
                {
                    // 如果两个端口都有上下文并且不兼容，那么检查一下port2是否能在不通过当前的连接抵达上下文源头
                    // port2作为一个input ListPort，同时只会有1个连接
                    List<uint> lpIds = new();
                    PickAllLinkedPortIdsOfPort(port2, lpIds);
                    LinkControl link;
                    if (lpIds.Count > 0)
                    {
                        var liId = lpIds[0];
                        link = GetLink(liId, port2.Id);
                        if (CanPortReachContextSourceWithoutCertainLink(port2, link))
                        {
                            return false;
                        }
                    }
                    else
                    {
                        return false;
                    }
                }
            }

            var typeSpreadMap1 = GetTypeSpreadMapFromPort(port1);

            List<uint> lpIdLinkedToPort2 = new();
            PickAllLinkedPortIdsOfPort(port2, lpIdLinkedToPort2);
            LinkControl linkToIgnore = null;
            if (lpIdLinkedToPort2.Count > 0)
            {
                var lpId = lpIdLinkedToPort2[0];
                linkToIgnore = GetLink(lpId, port2.Id);
            }

            var typeSpreadMap2 = GetTypeSpreadMapFromPort(port2, linkToIgnore);

            TypeSpreadMapLinkResult linkResult = new(typeSpreadMap1, typeSpreadMap2);

            if (!linkResult.IsCompatible)
            {
                return false;
            }

            /// 环状连接检查
            return PassAcyclicCheck(port1, port2);
        }

        public bool CanPortsBeLinked(VM_Port port1, VM_Port port2, out TypeSpreadMapLinkResult tpSpreadLinkResult)
        {
            tpSpreadLinkResult = null;
            var ports = MakeSurePortsOrder(port1, port2);
            if (!ports.Item3)
            {
                return false;
            }

            port1 = ports.Item1;
            port2 = ports.Item2;

            var ioTypeCheck = port1.IOType != EPortIOType.Unknown
                        && port2.IOType != EPortIOType.Unknown
                        && port1.IOType != port2.IOType;

            if (!ioTypeCheck)
            {
                return false;
            }

            // FlowPort不需要做更多的检查
            if (port1.IsFlowPort && port2.IsFlowPort)
            {
                /// 环状连接检查
                return PassAcyclicCheck(port1, port2);
            }

            if (_linkMap.ContainsKey((port1.Id, port2.Id)))
            {
                return false;
            }

            /// 端口双方必须同时为List或都不是才能连接
            if (port1.IsListPort != port2.IsListPort)
            {
                return false;
            }

            /// 两个List端口连接时需要通过Context兼容性测试
            if (port1.IsListPort && port2.IsListPort)
            {
                var context1 = _contextMap.GetPortContextId(port1.Id);
                var context2 = _contextMap.GetPortContextId(port2.Id);
                if (!DAGHelper.AreContextsCompatible(context1, context2))
                {
                    // 如果练个端口都有上下文并且不兼容，那么检查一下port2是否能在不通过当前的连接抵达上下文源头
                    // port2作为一个input ListPort，同时只会有1个连接
                    List<uint> lpIds = new();
                    PickAllLinkedPortIdsOfPort(port2, lpIds);
                    if (lpIds.Count > 0)
                    {
                        var liId = lpIds[0];
                        var link = GetLink(liId, port2.Id);
                        if (CanPortReachContextSourceWithoutCertainLink(port2, link))
                        {
                            return false;
                        }
                    }
                    else
                    {
                        return false;
                    }
                }
            }

            var typeSpreadMap1 = GetTypeSpreadMapFromPort(port1);

            List<uint> lpIdLinkedToPort2 = new();
            PickAllLinkedPortIdsOfPort(port2, lpIdLinkedToPort2);
            LinkControl linkToIgnore = null;
            if (lpIdLinkedToPort2.Count > 0)
            {
                var lpId = lpIdLinkedToPort2[0];
                linkToIgnore = GetLink(lpId, port2.Id);
            }

            var typeSpreadMap2 = GetTypeSpreadMapFromPort(port2, linkToIgnore);

            tpSpreadLinkResult = new(typeSpreadMap1, typeSpreadMap2);
            if (!tpSpreadLinkResult.IsCompatible)
            {
                return false;
            }

            /// 环状连接检查
            return PassAcyclicCheck(port1, port2);
        }

        private (VM_Port, VM_Port, bool) MakeSurePortsOrder(VM_Port port1, VM_Port port2)
        {
            if (port1.IOType == EPortIOType.Output && port2.IOType == EPortIOType.Input)
            {
                return (port1, port2, true);
            }
            else if (port1.IOType == EPortIOType.Input && port2.IOType == EPortIOType.Output)
            {
                return (port2, port1, true);
            }
            else
            {
                return (null, null, false);
            }
        }

        private void ProcessPortTypeChanging(TypeSpreadMapLinkResult linkResult)
        {
            if (linkResult.IsCompatible)
            {
                foreach (var p in linkResult.PortsToChangeType)
                {
                    p.ParentNode.TryChangePortToType(p, linkResult.DataType);
                }
            }
        }

        /// <summary>
        /// 返回（新建的LinkControl, 新建的SubPort(可能为Null), 被解除掉的连接（对独占接口建立新连接，会把之前已有的连接解除））
        /// </summary>
        /// <param name="port1"></param>
        /// <param name="port2"></param>
        /// <returns></returns>
        public (LinkControl, VM_Port, List<(uint, uint)>) CreateNewLink(VM_Port port1, VM_Port port2)
        {
            List<(uint, uint)> replacedLinks;
            var ports = MakeSurePortsOrder(port1, port2);
            if (ports.Item1 != null && ports.Item2 != null)
            {
                var fromPort = ports.Item1;
                var toPort = ports.Item2;
                TypeSpreadMapLinkResult typeLinkResult;
                if (CanPortsBeLinked(fromPort, toPort, out typeLinkResult))
                {
                    VM_Port newExpandPort = null;
                    if (fromPort.IsExpandPort)
                    {
                        fromPort = fromPort.Expand();
                        newExpandPort = fromPort;
                    }

                    if (toPort.IsExpandPort)
                    {
                        toPort = toPort.Expand();
                        newExpandPort = toPort;
                    }

                    replacedLinks = new(2);
                    if (!fromPort.IsMultiConnectable)
                    {
                        var linkedPortIds = GetAllLinkedPortIdsOfPort_Private(fromPort);
                        if (linkedPortIds != null)
                        {
                            foreach (var pId in linkedPortIds)
                            {
                                replacedLinks.Add((fromPort.Id, pId));
                            }
                        }
                        DeleteAllLinksOnPort(fromPort);
                    }
                    if (!toPort.IsMultiConnectable)
                    {
                        var linkedPortIds = GetAllLinkedPortIdsOfPort_Private(toPort);
                        if (linkedPortIds != null)
                        {
                            foreach (var pId in linkedPortIds)
                            {
                                replacedLinks.Add((pId, toPort.Id));
                            }
                        }
                        DeleteAllLinksOnPort(toPort);
                    }

                    // 注意！此处处理两个端口同时可以变化到对方类型的情况
                    // 原则上以fromPort的类型为准
                    if (typeLinkResult != null)
                    {
                        ProcessPortTypeChanging(typeLinkResult);
                    }
                    //ProcessPortTypeChanging(fromPort, toPort);

                    var newLink = new LinkControl(fromPort, toPort);
                    newLink.OnLMBDownOnLink += _DAGVM.OnLMBDownOnLink;

                    _DAGVM.DAGCtrl.NodeCanvas.Children.Add(newLink);

                    _linkMap.Add((fromPort.Id, toPort.Id), newLink);
                    _linkingMap.Add(fromPort.Id, toPort.Id);

                    AddEdgeToTopology(newLink);

                    toPort.UpdatePortDisplayName();

                    fromPort.ParentNode.TryRefreshRelativeInspector();
                    toPort.ParentNode.TryRefreshRelativeInspector();

                    HandleContextOnNewLinkCreation(fromPort, toPort);

                    AddLinkData(fromPort.Id, toPort.Id);

                    return (newLink, newExpandPort, replacedLinks);
                }
            }

            return (null, null, null);
        }

        void HandleContextOnNewLinkCreation(VM_Port fromPort, VM_Port toPort)
        {
            HashSet<uint> startPorts = new();
            HashSet<VM_Node> arrivedNodes = new();
            HashSet<uint> arrivedPorts = new();

            if (DAGHelper.CanPortOutputContext(fromPort))
            {
                var contextId = DAGHelper.GetPortOutputContext(fromPort);
                _contextMap.AddContextSource(contextId, fromPort.Id);
                startPorts.Add(fromPort.Id);
                SpreadingContextToPort(toPort.Id, startPorts, arrivedNodes, arrivedPorts);
                _contextMap.AddContextSpreadedPorts(contextId, arrivedPorts);
                //RefreshContext(contextId);
            }
            else
            {
                var contextOfFromPort = _contextMap.GetPortContextId(fromPort.Id);
                var contextOfToPort = _contextMap.GetPortContextId(toPort.Id);
                // 上下文兼容问题在更早的阶段已经完成，此处如果上下文不同，只可能是其中只有一个具备上下文，此时进行传递
                if (contextOfFromPort != contextOfToPort)
                {
                    if (contextOfFromPort != DAGHelper.EmptyContextId)
                    {
                        startPorts.Add(fromPort.Id);
                        SpreadingContextToPort(toPort.Id, startPorts, arrivedNodes, arrivedPorts);
                        _contextMap.AddContextSpreadedPorts(contextOfFromPort, arrivedPorts);
                        //RefreshContext(contextOfFromPort);
                    }
                    else if (contextOfToPort != DAGHelper.EmptyContextId)
                    {
                        startPorts.Add(toPort.Id);
                        SpreadingContextToPort(fromPort.Id, startPorts, arrivedNodes, arrivedPorts);
                        _contextMap.AddContextSpreadedPorts(contextOfToPort, arrivedPorts);
                        //RefreshContext(contextOfToPort);
                    }
                }
            }
        }

        void DeleteLink(uint fromPortId, uint toPortId)
        {
            var link = _linkMap[(fromPortId, toPortId)];

            RemoveEdgeFromTopology(link);
            _DAGVM.DAGCtrl.NodeCanvas.Children.Remove(link);
            link.ClearPortListoner();

            _linkMap.Remove((fromPortId, toPortId));
            _linkingMap.Remove(fromPortId, toPortId);

            var fromPort = link.FromPort;
            var toPort = link.ToPort;

            toPort.UpdatePortDisplayName();

            fromPort.ParentNode.TryRefreshRelativeInspector();
            toPort.ParentNode.TryRefreshRelativeInspector();

            if (fromPort.IsListPort)
            {
                var contextId = _contextMap.GetPortContextId(fromPort.Id);
                RefreshContext(contextId);
            }

            RemoveLinkData(fromPortId, toPortId);
        }

        public void DeleteLink(VM_Port port1, VM_Port port2)
        {
            if (ArePortsLinked(port1.Id, port2.Id))
            {
                var ports = MakeSurePortsOrder(port1, port2);
                DeleteLink(ports.Item1.Id, ports.Item2.Id);
            }
        }

        public void DeleteLink(LinkControl link)
        {
            DeleteLink(link.FromPort, link.ToPort);
        }

        public bool DeleteAllLinksOnPort(VM_Port port)
        {
            HashSet<uint> linkedIds;
            if (_linkingMap.TryGetBinds(port.Id, out linkedIds))
            {
                _linkingMap.Remove(port.Id);
                foreach (var anotherPortId in linkedIds)
                {
                    var anotherPort = _DAGVM.GetPort(anotherPortId);
                    var ports = MakeSurePortsOrder(port, anotherPort);

                    var link = _linkMap[(ports.Item1.Id, ports.Item2.Id)];

                    RemoveEdgeFromTopology(link);
                    _DAGVM.DAGCtrl.NodeCanvas.Children.Remove(link);
                    link.ClearPortListoner();

                    _linkMap.Remove((ports.Item1.Id, ports.Item2.Id));

                    var fromPort = link.FromPort;
                    var toPort = link.ToPort;

                    toPort.UpdatePortDisplayName();

                    fromPort.ParentNode.TryRefreshRelativeInspector();
                    toPort.ParentNode.TryRefreshRelativeInspector();

                    RemoveLinkData(fromPort.Id, toPort.Id);
                }

                if (port.IsListPort)
                {
                    var contextId = _contextMap.GetPortContextId(port.Id);
                    RefreshContext(contextId);
                }
                return true;
            }
            return false;
        }

        public void ChangeDestPortOfLink(VM_Port fromPort, VM_Port prevDestPort, VM_Port newDestPort)
        {
            DeleteLink(fromPort, prevDestPort);
            CreateNewLink(fromPort, newDestPort);
        }

        /// <summary>
        /// 注意，此函数不直接传递HashSet，而是将内部数据拷贝到传入的HashSet中，以防止外部通过引用对内部数据进行意外的篡改
        /// </summary>
        /// <param name="port"></param>
        /// <returns></returns>
        public void PickAllLinkedPortIdsOfPort(VM_Port port, List<uint> linkedToPortIds)
        {
            linkedToPortIds.Clear();
            HashSet<uint> linkedIds;
            if (_linkingMap.TryGetBinds(port.Id, out linkedIds))
            {
                linkedToPortIds.Capacity = linkedIds.Count;
                foreach (var id in linkedIds)
                {
                    linkedToPortIds.Add(id);
                }
            }
        }

        /// <summary>
        /// 注意，此函数复制一份HashSet，而不是直接传递HashSet，以防止外部通过引用对内部数据进行意外的篡改
        /// </summary>
        /// <param name="port"></param>
        /// <returns></returns>
        public HashSet<uint> GetAllLinkedPortIdsOfPort(VM_Port port)
        {
            HashSet<uint> result = new HashSet<uint>();
            HashSet<uint> linkedIds;
            if (_linkingMap.TryGetBinds(port.Id, out linkedIds))
            {
                result.UnionWith(linkedIds);
            }
            return result;
        }

        /// <summary>
        /// 注意，此函数直接返回内部HashSet，搜索失败时返回null，为防止外部通过引用对内部数据进行意外的篡改，不要设为public
        /// </summary>
        /// <param name="port"></param>
        /// <returns></returns>
        private HashSet<uint> GetAllLinkedPortIdsOfPort_Private(VM_Port port)
        {
            _linkingMap.TryGetBinds(port.Id, out HashSet<uint> linkedIds);
            return linkedIds;
        }

        public bool IsPortLinked(VM_Port port)
        {
            return _linkingMap.ContainsKey(port.Id);
        }

        public int GetPortLinkCount(VM_Port port)
        {
            if (_linkingMap.TryGetBinds(port.Id, out HashSet<uint> binds))
            {
                return binds.Count;
            }
            return 0;
        }

        public LinkControl GetLink(uint portId1, uint portId2)
        {
            LinkControl link;
            if (_linkMap.TryGetValue((portId1, portId2), out link))
            {
                return link;
            }
            else if (_linkMap.TryGetValue((portId2, portId1), out link))
            {
                return link;
            }
            return null;
        }

        public bool ArePortsLinked(VM_Port port1, VM_Port port2)
        {
            return _linkMap.ContainsKey((port1.Id, port2.Id)) || _linkMap.ContainsKey((port2.Id, port1.Id));
        }

        public bool ArePortsLinked(uint portId1, uint portId2)
        {
            return _linkMap.ContainsKey((portId1, portId2)) || _linkMap.ContainsKey((portId2, portId1));
        }

        void AddEdgeToTopology(LinkControl link)
        {
            if (link.FromPort != null && link.ToPort != null)
            {
                var fromNode = link.FromPort.ParentNode;
                var toNode = link.ToPort.ParentNode;

                Dictionary<VM_Node, HashSet<LinkControl>> directDests;
                if (!_nodeTopology.TryGetValue(fromNode, out directDests))
                {
                    directDests = new Dictionary<VM_Node, HashSet<LinkControl>>();
                    _nodeTopology.Add(fromNode, directDests);
                }
                HashSet<LinkControl> viaLinks;
                if (!directDests.TryGetValue(toNode, out viaLinks))
                {
                    viaLinks = new HashSet<LinkControl>();
                    directDests.Add(toNode, viaLinks);
                }
                viaLinks.Add(link);
            }
        }

        void RemoveEdgeFromTopology(LinkControl link)
        {
            if (link.FromPort != null && link.ToPort != null)
            {
                var fromNode = link.FromPort.ParentNode;
                var toNode = link.ToPort.ParentNode;
                Dictionary<VM_Node, HashSet<LinkControl>> directDests;
                if (_nodeTopology.TryGetValue(fromNode, out directDests))
                {
                    HashSet<LinkControl> viaLinks;
                    if (directDests.TryGetValue(toNode, out viaLinks))
                    {
                        viaLinks.Remove(link);
                        if (viaLinks.Count == 0)
                        {
                            directDests.Remove(toNode);
                        }
                    }
                    if (directDests.Count == 0)
                    {
                        _nodeTopology.Remove(fromNode);
                    }
                }
            }
        }

        public bool PassAcyclicCheck(VM_Node fromNode, VM_Node toNode)
        {
            if (fromNode == toNode)
            {
                return false;
            }

            Dictionary<VM_Node, HashSet<LinkControl>> directDests;
            if (_nodeTopology.TryGetValue(toNode, out directDests))
            {
                if (directDests.ContainsKey(fromNode))
                {
                    return false;
                }
                else
                {
                    var pass = true;
                    foreach (var dest in directDests)
                    {
                        pass &= PassAcyclicCheck(fromNode, dest.Key);
                    }
                    return pass;
                }
            }
            else return true;
        }

        public bool PassAcyclicCheck(VM_Port port1, VM_Port port2)
        {
            if (port1.IsFlowPort && port2.IsFlowPort && AllowCyclicFlow)
            {
                return true;
            }

            var ports = MakeSurePortsOrder(port1, port2);
            if (ports.Item1 != null && ports.Item2 != null)
            {
                var fromNode = ports.Item1.ParentNode;
                var toNode = ports.Item2.ParentNode;
                return PassAcyclicCheck(fromNode, toNode);
            }
            else return false;
        }

        public void OnAddNodeToDAG(VM_Node node)
        {
            foreach (var p in node.AllPorts)
            {
                var port = p.Value;
                if (port.IsListPort && port.IOType == EPortIOType.Output)
                {
                    var contextId = DAGHelper.GetPortOutputContext(port);
                    if (contextId != DAGHelper.EmptyContextId)
                    {
                        _contextMap.AddContextSource(contextId, port.Id);
                    }
                }
            }
        }

        public void OnRemoveNodeFromDAG(VM_Node node)
        {
            HashSet<uint> contextToRefresh = new();
            foreach (var p in node.AllPorts)
            {
                var port = p.Value;
                if (port.IsListPort)
                {
                    contextToRefresh.Add(_contextMap.GetPortContextId(port.Id));
                    if (port.IOType == EPortIOType.Output && port.ContextMode != EPortContextMode.Relay)
                    {
                        _contextMap.RemoveContextSource(port.Id);
                    }
                }
            }
            foreach (var c in contextToRefresh)
            {
                RefreshContext(c);
            }
        }

        bool CanPortReachContextSourceWithoutCertainLink(VM_Port port, LinkControl certainLink)
        {
            HashSet<uint> contextSrcPortIds = new();

            var contextId = _contextMap.GetPortContextId(port.Id);
            _contextMap.PickContextSources(contextId, contextSrcPortIds);

            HashSet<uint> arrivedPorts = new();

            return TravelViaContextFromPort(port, certainLink, arrivedPorts, contextSrcPortIds);
        }

        bool TravelViaContextFromPort(VM_Port port, LinkControl certainLink,
            HashSet<uint> arrivedPortIds, HashSet<uint> contextSrcPortIds)
        {
            if (DAGHelper.CanPortOutputContext(port))
            {
                return true;
            }

            arrivedPortIds.Add(port.Id);

            List<uint> linkedPortIds = new();
            PickAllLinkedPortIdsOfPort(port, linkedPortIds);

            var reachedContextSrouce = false;
            foreach (var lpId in linkedPortIds)
            {
                var currLink = GetLink(port.Id, lpId);
                if (currLink != certainLink)
                {
                    if (contextSrcPortIds.Contains(lpId))
                    {
                        reachedContextSrouce = true;
                        break;
                    }
                    else if (!arrivedPortIds.Contains(lpId))
                    {
                        var linkedPort = _DAGVM.GetPort(lpId);
                        reachedContextSrouce = TravelViaContextFromPort(linkedPort, certainLink, arrivedPortIds, contextSrcPortIds);
                        if (reachedContextSrouce)
                        {
                            break;
                        }
                    }
                }
            }
            if (reachedContextSrouce)
            {
                return true;
            }
            else
            {
                if (DAGHelper.CanPortRelayContext(port))
                {
                    foreach (var p in port.ParentNode.RelayContextPorts)
                    {
                        if (p != port && !arrivedPortIds.Contains(p.Id))
                        {
                            reachedContextSrouce = TravelViaContextFromPort(p, certainLink, arrivedPortIds, contextSrcPortIds);
                            if (reachedContextSrouce)
                            {
                                break;
                            }
                        }
                    }
                }
            }

            return reachedContextSrouce;
        }

        TypeSpreadMap GetTypeSpreadMapFromPort(VM_Port port, LinkControl ignoreLink = null)
        {
            TypeSpreadMap result = new();
            BuildUpTypeSpreadMapFromPort(port, ignoreLink, result);
            return result;
        }

        void BuildUpTypeSpreadMapFromPort(VM_Port port, LinkControl ignoreLink, TypeSpreadMap result)
        {
            if (result.ImmutableTypePorts.Contains(port) || result.MutableTypePorts.Contains(port))
            {
                return;
            }

            if (result.CurrentDataType == null)
            {
                result.CurrentDataType = port.DataType;
            }

            if (port.TypeGroupId == DAGHelper.InvalidTypeGroupId)
            {
                if (result.ImmutableTypePorts.Count == 0)
                {
                    result.ImmutableTypePorts.Add(port);
                    result.AllowedTypes.Clear();
                    result.AllowedTypes.Add(result.CurrentDataType);
                }
                else
                {
                    if (result.CurrentDataType == port.DataType)
                    {
                        result.ImmutableTypePorts.Add(port);
                    }
                    else
                    {
                        throw new NotImplementedException();
                    }
                }
            }
            else
            {
                var tgi = port.ParentNode.GetTypeGroupInfo(port.TypeGroupId);
                if (result.MutableTypePorts.Count == 0 && result.AllowedTypes.Count == 0)
                {
                    result.AllowedTypes.UnionWith(tgi.AllowedTypes);
                }
                else
                {
                    result.AllowedTypes.IntersectWith(tgi.AllowedTypes);
                }

                result.MutableTypePorts.UnionWith(tgi.PortMembers);

                foreach (var p in tgi.PortMembers)
                {
                    if (p != port)
                    {
                        BuildTypeSpreadMapForLinkedPorts(p, ignoreLink, result);
                    }
                }
            }

            BuildTypeSpreadMapForLinkedPorts(port, ignoreLink, result);
        }

        void BuildTypeSpreadMapForLinkedPorts(VM_Port fromPort, LinkControl ignoreLink, TypeSpreadMap result)
        {
            List<uint> linkedPortIds = new();
            PickAllLinkedPortIdsOfPort(fromPort, linkedPortIds);

            foreach (var lpid in linkedPortIds)
            {
                var linkedPort = _DAGVM.GetPort(lpid);
                if (linkedPort != null)
                {
                    var ports = MakeSurePortsOrder(fromPort, linkedPort);
                    if (ignoreLink != null && ignoreLink.FromPort == ports.Item1 && ignoreLink.ToPort == ports.Item2)
                    {
                        continue;
                    }
                    else
                    {
                        BuildUpTypeSpreadMapFromPort(linkedPort, ignoreLink, result);
                    }
                }
            }
        }

        public void RefreshContext(uint contextId)
        {
            if (contextId == DAGHelper.EmptyContextId)
            {
                return;
            }

            _contextMap.ClearContextSpreadedPorts(contextId);

            HashSet<uint> sourcePorts = new();
            _contextMap.PickContextSources(contextId, sourcePorts);

            HashSet<uint> arrivedPorts = new();
            foreach (var pId in sourcePorts)
            {
                var currPortVM = _DAGVM.GetPort(pId);
                List<uint> linkedToPortIds = new();
                PickAllLinkedPortIdsOfPort(currPortVM, linkedToPortIds);
                arrivedPorts.UnionWith(linkedToPortIds);
            }
            SpreadingContextToMultiPorts(contextId, sourcePorts, arrivedPorts);
        }

        void SpreadingContextToMultiPorts(uint spreadingContextId, HashSet<uint> startPorts, HashSet<uint> destPorts)
        {
            HashSet<uint> spreadToPorts = new();
            HashSet<VM_Node> arrivedNodes = new();

            foreach (var destPortID in destPorts)
            {
                SpreadingContextToPort(destPortID, startPorts, arrivedNodes, spreadToPorts);
            }

            _contextMap.AddContextSpreadedPorts(spreadingContextId, spreadToPorts);
        }

        void SpreadingContextToPort(uint destPortId, HashSet<uint> startPorts, HashSet<VM_Node> arrivedNodes, HashSet<uint> arrivedPorts)
        {
            var currLinkedPort = _DAGVM.GetPort(destPortId);

            if (currLinkedPort.CanRelayContext && arrivedPorts.Add(destPortId))
            {
                var node = currLinkedPort.ParentNode;
                if (arrivedNodes.Add(node))
                {
                    List<uint> linkedPortIds = new();
                    foreach (var otherRelayPort in node.RelayContextPorts)
                    {
                        linkedPortIds.Clear(); // 为了防止下面的if未通过，导致上一次循环中Pick的结果被保留
                        if (arrivedPorts.Add(otherRelayPort.Id))
                        {
                            PickAllLinkedPortIdsOfPort(otherRelayPort, linkedPortIds);
                            foreach (var nextLinkedPortId in linkedPortIds)
                            {
                                var alreadyArrived = arrivedPorts.Contains(nextLinkedPortId);
                                var isStartPort = startPorts.Contains(nextLinkedPortId);
                                if (!alreadyArrived && !isStartPort)
                                {
                                    SpreadingContextToPort(nextLinkedPortId, startPorts, arrivedNodes, arrivedPorts);
                                }
                            }
                        }
                    }
                }
            }
        }

        void AddLinkData(uint fromPortId, uint toPortId)
        {
            HashSet<uint> destIds;
            if (!WorkingLinkData.TryGetValue(fromPortId, out destIds))
            {
                destIds = new HashSet<uint>();
                WorkingLinkData.Add(fromPortId, destIds);
            }
            destIds.Add(toPortId);
        }

        void RemoveLinkData(uint fromPortId, uint toPortId)
        {
            HashSet<uint> destIds;
            if (WorkingLinkData.TryGetValue(fromPortId, out destIds))
            {
                destIds.Remove(toPortId);
                if (destIds.Count == 0)
                {
                    WorkingLinkData.Remove(fromPortId);
                }
            }
        }
    }
}
